<?php

class spElement
{
	private $tag;						// string
	private $arAttr;					// array of (key=>value) where key: attribute name AND value: attribute value
	private $arStyle;					// array of (key=>value) where key: style name AND value: style value
	private $parent = null;				// parenet element (object)
	private $arChild;					// children elements (objects)
	private $bMustHaveEndingTag;		// bool
	private $bMustEndTag;				// bool
	private $elementDataString = "";	// string
	private $data;			

	private $data_index	= false;		// mix (bool, value) index for $_REQUEST arrays
	
	public function __clone()
	{
	//echo "spElement __clone called</br>";
		$parent = null;
		
		$arChild = array();
		foreach ( $this->arChild as $c )
		{
			$newChild = clone $c;
			$newChild->parent = $this;
			$arChild[] = $newChild;
		}
		$this->arChild = $arChild;
	}
	
	public function __construct( $tag=null, $data=null )
	{
		$this->data = array_merge(
			array(
				"id"			=> "",			// string/value
				"name_id"		=> "",			// string, unique name for spElement (not for the html code!)
				"content" 		=> "",			// mix: string, spElement
				"contentAfter" 	=> "",			// mix: string, spElement
				"class"			=> "",			// string
				"attr"			=> array(),		// key=>value (strings)
				"style"			=> array(),		// key=>value (strings)
				"readonly"		=> false,		// bool
				"title"			=> ""			// string
			),
			$data==null?array():$data
		);
	
		$this->bMustHaveEndingTag 	= false;
		$this->bMustEndTag 			= false;
		$this->tag 					= $tag;
		$this->arAttr 				= array();
		$this->arStyle 				= array();
		$this->arChild 				= array();
		
		$this->addDataArray( $this->data );
		
		if ( $this->data["id"]!="" )
			$this->addAttr( "id", $this->data["id"] );

		if ( $this->data["title"]!="" )
			$this->addAttr( "title", $this->data["title"] );
			
		if ( count($this->data["style"]) )
		{
			foreach ( $this->data["style"] as $k => $v )
			{
				$this->addStyle( $k, $v );
			}
		}
		
		if ( isset($data["onclick"]) )
			$this->setOnClick( $data["onclick"] );

		if ( isset($data["child"]) )
			$this->addChild( $data["child"] );
	}
	
	public function setDataIndex( $index )
	{
		$this->data_index = $index;
		foreach ( $this->arChild as &$c )
			$c->setDataIndex( $index );
	}
	
	public function dumpInfo( $indent=0 )
	{
		$ind = "";
		for ($i=0; $i<$indent; $i++ )
			$ind.= "&nbsp;&nbsp;&nbsp;";
	
		$class_name = get_class($this);
	
		echo $ind."<b>class: ".$class_name."</b><br/>";
		$ind.= "&nbsp;&nbsp;";
		
		echo $ind."id= ".$this->getId()."<br/>";
		echo $ind."data_index= ".$this->getDataIndex()."<br/>";
	
		if ( $class_name=="spInput" )
		{
			echo $ind."name=".$this->getName()."<br/>";
			echo $ind."value=".$this->getValue()."<br/>";
		}
	
		foreach ( $this->arChild as $c )
			$c->dumpInfo( $indent+1 );
	}
	
	public function getDataIndex()	
	{
		return $this->data_index;
	}
	
	public function setOnClick( $onclick )
	{
		$this->addAttr( "onclick", $onclick );
	}
	
	public function setId( $id )
	{
		$this->data["id"] = $id;
		$this->addAttr( "id", $id );
	}
	
	public function getId()
	{
		return $this->getAttr( "id" );
	}
	
	public function removeId()
	{
		$this->data["id"] = "";
		$this->removeAttr( "id" );
	}
	
	public function setReadOnly( $readonly )
	{
		$this->data["readonly"] = $readonly;
	}

	public function getNumChildren()
	{
		return count($this->arChild);
	}
	
	public function getChild( $index )
	{
		return $this->arChild[$index];
	}
	
	public function getChildrenArray()
	{
		return $this->arChild;
	}
	
	public function updateFromRequest()
	{
		foreach ( $this->arChild as &$c )
			$c->updateFromRequest();
	}	
	
	public function setElementDataString( $s )
	{
		$this->elementDataString = $s;
	}
	
	public function getElementDataString()
	{
		return $this->elementDataString;
	}	
	
	public function readOnly()
	{
		if ( $this->data["readonly"]=="true" )
			return true;
			
		if ( $this->getParent()==null )
			return false;
			
		return $this->getParent()->readOnly();
	}

	public function addDataArray( $data )
	{
		if ( $data==null )
			return;
	
		foreach ( $data as $k => $v )
			$this->addData( $k, $v );
	}
	
	public function addData( $key, $value )
	{
		$data = &$this->data;
		
		$data[$key] = $value;
		
		if ( isset($data["class"]) )
			$this->setClass( $data["class"] );

		if ( isset($data["attr"]) )
			$this->addAttrArray( $data["attr"] );
			
		return $data;
	}
		
	public function getData( $index=null )
	{
		if ( $index!=null )
			return $this->data[$index];
	
		return $this->data;
	}

	public function setElementNameId( $name_id )
	{
		$this->data["name_id"] = $name_id;
	}

	// i.e. <p></p> where </p> is the ending tag
	public function setMustHaveEndingTag( $bool )
	{
		$this->bMustHaveEndingTag = $bool;
	}
	
	// i.e. <img></img> is invalid and must end as <img ... />
	public function setMustEndTag( $bool )
	{
		$this->bMustEndTag = $bool;
	}

	public function setTag( $tag )
	{
		$this->tag = $tag;
	}
	
	public function getTag()
	{
		return $this->tag;
	}

	public function setClass( $class )
	{
		$this->data["class"] = $class;
	}
	
	public function addClass( $class )
	{
		if ( $this->data["class"]!="" )
			$this->data["class"].= " ";
			
		$this->data["class"].= $class;
	}

	public function addStyle( $name, $value="" )
	{
		// handle array of styles in form of key=>value
		if ( is_array($name) )
		{
			foreach ( $name as $k => $v )
				$this->arStyle[$k] = $v;

			return;
		}
	
		$this->arStyle[$name] = $value;
	}
	
	public function removeStyle( $name )
	{
		unset( $this->arStyle[$name] );
	}
	
	public function addAttrArray( $ar )
	{
		foreach ( $ar as $k => $v )
		{
			$this->addAttr( $k, $v );
		}
	}
	
	public function removeAttr( $name )
	{
		unset( $this->arAttr[$name] );
	}

	public function addAttr( $name, $value )
	{
		$this->arAttr[$name] = $value;
	}

	public function getAttr( $name )
	{
		if ( !isset($this->arAttr[$name] ) )
			return null;
	
		return $this->arAttr[$name];
	}

	public function setParent( $parent )
	{
		$this->parent = $parent;
	}

	public function getParent()
	{
		return $this->parent;
	}

	/*
		Adds the element to the end of the array.
	*/
	
	public function addChild( $element )
	{
		$element->setParent( $this );
		$this->arChild[] = $element;
	}
	
	public function removeChild( $element )
	{
		if(	($key = array_search($element, $this->arChild))!==FALSE ) {
			unset( $this->arChild[$key] );
		}
	}
	
	/*
		Inserts the element to the beginning of the array.
	*/

	public function insertChild( $element )
	{
		array_unshift( $this->arChild, $element );
	}

	public function removeLastChild()
	{
		array_pop( $this->arChild );
	}

	public function find( $name_id )
	{
		if ( $this->data["name_id"]==$name_id )
		{
			return $this;
		}

		// sanity check
		if ( !is_array($this->arChild) )
		{
			echo "ERROR: an element seems to be invalid, this can be caused if a derived element didn't call it's parent construct";
			var_dump( $this );
		}
		
		foreach ( $this->arChild as $c )
		{
			$found = $c->find( $name_id );
			if ( $found!=null )
				return $found;
		}

		return false;
	}
	
	// fixme: not that good,.. make it better
	public function dump()
	{
		$s = array(
			"tag" => $this->tag
		);
		
		if ( count($this->arChild) )
		{
			$c = array();
			foreach ( $this->arChild as $cc )
			{
				$c[] = $cc->dump();
			}
			
			$s[] = array( "children", $c );
		}

		return $s;
	}
	
	public function findTagExt( $tag, &$num=0 )
	{
		if ( $this->tag==$tag )
		{
			if ( $num==0 )
				return $this;
				
			$num--;
		}

		foreach ( $this->arChild as $c )
		{
			$found = $c->findTagExt( $tag, $num );
			if ( $found!=null )
				return $found;
		}

		return null;
	}
	
	// returns the first found tag
	public function findTag( $tag )
	{
		if ( $this->tag==$tag )
		{
			return $this;
		}

		foreach ( $this->arChild as $c )
		{
			$found = $c->findTag( $tag );
			if ( $found!=null )
				return $found;
		}

		return null;
	}	

	public function setContent( $content )
	{
		$this->data["content"] = $content;
	}
	
	public function appendContent( $content )
	{
		$this->data["content"].= $content;
	}

	public function setContentAfter( $content )
	{
		$this->data["contentAfter"] = $content;
	}

	public function renderSpaces( $count )
	{
		$c = $count;
		while ( $c )
		{
			echo "    ";
			$c--;
		}
	}

	public function process()
	{
		// process all children
		if ( count($this->arChild) )
		{
			foreach ( $this->arChild as $c )
			{
				$c->process();
			}
		}
	}
	
	public function getHtml()
	{
		ob_start();
		$this->render();
		$html = ob_get_contents();
		ob_end_clean();
		return $html;
	}

	public function render( $indent=0 )
	{
		$this->renderSpaces( $indent );

		if ( $this->tag==null )
		{
			if ( isset($this->data["content"]) )
			{
				echo $this->data["content"];
			}
			
			return;
		}

		echo "<$this->tag";
		
		// class
		if ( $this->data["class"]!="" )
		{
			echo " class='".$this->data["class"]."'";
		}

		// styles
		if ( count($this->arStyle) )
		{
			echo " style='";
			foreach ( $this->arStyle as $k => $v )
			{
				//echo $s["name"].":".$s["value"].";";
				echo $k.":".$v.";";
			}
			echo "'";
		}

		// attributes
		if ( count($this->arAttr) )
		{
			foreach ( $this->arAttr as $name => $value )
			{
				echo " ".$name."='".$value."'";
			}
		}

		if ( empty($this->data["content"]) && count($this->arChild)==0 && $this->bMustHaveEndingTag==false )
		{
			echo "/>\n";
			return;
		}
		
		if ( $this->bMustEndTag==true )
		{
			echo "/>\n";
			echo $this->data["content"];
			
			if ( $this->data["contentAfter"] instanceof spElement )
				$this->data["contentAfter"]->render();
			else
				echo $this->data["contentAfter"];
			
			return;
		}
	
		echo ">";
		
		if ( $this->data["content"] instanceof spElement )
			$this->data["content"]->render();
		else
			echo $this->data["content"];

		if ( count($this->arChild)!=0 )
			echo "\n";

		if ( count($this->arChild) )
		{
			foreach ( $this->arChild as $c )
			{
				if ( !method_exists( $c, "render" ) )
					var_dump(get_class_methods($c) );

				$c->render( $indent+1 );
			}
		}

		if ( $this->data["contentAfter"] instanceof spElement )
			$this->data["contentAfter"]->render();
		else
			echo $this->data["contentAfter"];

		if ( count($this->arChild)!=0 )
			$this->renderSpaces( $indent );
		
		echo "</$this->tag>\n";
	}
}

?>